// Copyright 2018 orivil.com. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found at https://mit-license.org.

package handlers

import (
	"encoding/base64"
	"errors"
	"gitee.com/tugoer/psys/models"
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
	"sync"
)

var ErrNeedBasicAuthorization = errors.New("need basic authorization")

func Auth(ctx *gin.Context) {
	admin := GetAdminFromSession(ctx)
	if admin == nil {
		ctx.AbortWithError(http.StatusForbidden, errors.New(http.StatusText(http.StatusForbidden)))
	} else {
		ctx.Next()
	}
}

type AdminSessions struct {
	data map[string]*models.Admin
	mu   sync.RWMutex
}

func (as *AdminSessions) Set(key string, admin *models.Admin) {
	as.mu.Lock()
	defer as.mu.Unlock()
	as.data[key] = admin
}

func (as *AdminSessions) Get(key string) (admin *models.Admin) {
	if key != "" {
		as.mu.RLock()
		defer as.mu.RUnlock()
		return as.data[key]
	}
	return nil
}

func (as *AdminSessions) Del(key string) {
	as.mu.Lock()
	defer as.mu.Unlock()
	delete(as.data, key)
}

var AdminSessionContainer = &AdminSessions{
	data: make(map[string]*models.Admin, 10),
}

func SetAdminToSession(username string, admin *models.Admin) {
	AdminSessionContainer.Set(username, admin)
}

var loginContextKey = "login-admin"

func GetAdminFromSession(ctx *gin.Context) *models.Admin {
	ad, ok := ctx.Get(loginContextKey)
	if ok {
		return ad.(*models.Admin)
	}
	auth := GetAuthorization(ctx)
	if auth != "" {
		username, password, err := DecodeBasicAuthorization(auth)
		if err == nil {
			admin := AdminSessionContainer.Get(username)
			if admin == nil {
				admin, _ = SignIn(username, password)
				if admin != nil {
					AdminSessionContainer.Set(username, admin)
				}
			}
			if admin != nil {
				ctx.Set(loginContextKey, admin)
				return admin
			}
		}
	}
	return nil
}

func GetAuthorization(ctx *gin.Context) (auth string) {
	return ctx.Request.Header.Get("Authorization")
}

func EncodeBasicAuthorization(username, password string) (auth string, err error) {
	//username, err = crypto.EncryptString(username)
	//if err != nil {
	//	return "", err
	//} else {
	//	password, err = crypto.EncryptString(password)
	//	if err != nil {
	//		return "", err
	//	} else {
	auth = username + ":" + password
	return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)), nil
	//}
	//}
}

func DecodeBasicAuthorization(auth string) (username, password string, err error) {
	username, password, ok := parseBasicAuth(auth)
	if !ok {
		return "", "", ErrNeedBasicAuthorization
	}
	//username, err = crypto.DecryptString(username)
	//if err != nil {
	//	return "", "", err
	//}
	//password, err = crypto.DecryptString(password)
	//if err != nil {
	//	return "", "", err
	//}
	return username, password, nil
}

func DelAdminFromSession(username string) {
	AdminSessionContainer.Del(username)
}

// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
func parseBasicAuth(auth string) (username, password string, ok bool) {
	const prefix = "Basic "
	if !strings.HasPrefix(auth, prefix) {
		return
	}
	c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}
